home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / stk-3.002 / stk-3 / STk-3.1 / Lib / menu.stk < prev    next >
Encoding:
Text File  |  1996-07-02  |  27.7 KB  |  802 lines

  1. ;;;;
  2. ;;;; Copyright ⌐ 1993-1996 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
  3. ;;;; 
  4. ;;;; Permission to use, copy, and/or distribute this software and its
  5. ;;;; documentation for any purpose and without fee is hereby granted, provided
  6. ;;;; that both the above copyright notice and this permission notice appear in
  7. ;;;; all copies and derived works.  Fees for distribution or use of this
  8. ;;;; software or derived works may only be charged with express written
  9. ;;;; permission of the copyright holder.  
  10. ;;;; This software is provided ``as is'' without express or implied warranty.
  11. ;;;;
  12. ;;;; This software is a derivative work of other copyrighted softwares; the
  13. ;;;; copyright notices of these softwares are placed in the file COPYRIGHTS
  14. ;;;;
  15. ;;;;
  16. ;;;;           Author: Erick Gallesio [eg@unice.fr]
  17. ;;;;    Creation date: 17-May-1993 12:35
  18. ;;;; Last file update:  2-Jul-1996 19:30
  19. ;;;;
  20.  
  21. ;; This file is loaded for the first menu or menub-button. Avoid to load it twice
  22. (unless (or (tk-command? Tk:menu) (tk-command? Tk:menubutton))
  23.   (let ()
  24.  
  25.  
  26.   (define tk::in-menu-button    #f)
  27.   (define tk::posted-mb        #f)
  28.   (define tk::popup        #f)
  29.   (define tk::grab-status    #f)
  30.   (define tk::old-grab        #f)
  31.  
  32. ;;-------------------------------------------------------------------------
  33. ;; Globals that are used in this file:
  34. ;;
  35. ;; cursor -        Saves the -cursor option for the posted menubutton.
  36. ;; focus -        Saves the focus during a menu selection operation.
  37. ;;            Focus gets restored here when the menu is unposted.
  38. ;; grab-status -    Used in conjunction with Tk::old-grab: if Tk:old-grab
  39. ;;            is not false, then Tk:grab-status contains either an
  40. ;;            empty string or "-global" to indicate whether the old
  41. ;;            grab was a local one or    a global one.
  42. ;; in-menu-button -    The name of the menubutton widget containing
  43. ;;            the mouse, or an empty string if the mouse is
  44. ;;            not over any menubutton.
  45. ;; old-grab -        Window that had the grab before a menu was posted.
  46. ;;            Used to restore the grab state after the menu
  47. ;;            is unposted.  Empty string means there was no
  48. ;;            grab previously set.
  49. ;; popup -        If a menu has been popped up via tk_popup, this
  50. ;;            gives the name of the menu.  Otherwise this
  51. ;;            value is empty.
  52. ;; posted-mb -        Name of the menubutton whose menu is currently
  53. ;;            posted, or an empty string if nothing is posted
  54. ;;            A grab is set on this widget.
  55. ;; relief -        Used to save the original relief of the current
  56. ;;            menubutton.
  57. ;; window -        When the mouse is over a menu, this holds the
  58. ;;            name of the menu;  it's cleared when the mouse
  59. ;;            leaves the menu.
  60. ;;-------------------------------------------------------------------------
  61.  
  62. ;;-------------------------------------------------------------------------
  63. ;; Overall note:
  64. ;; This file is tricky because there are four different ways that menus
  65. ;; can be used:
  66. ;;
  67. ;; 1. As a pulldown from a menubutton.  This is the most common usage.
  68. ;;    In this style, the variable tk::posted-mb identifies the posted
  69. ;;    menubutton.
  70. ;; 2. As a torn-off menu copied from some other menu.  In this style
  71. ;;    tk::posted-Mb is empty, and the top-level menu is no
  72. ;;    override-redirect.
  73. ;; 3. As an option menu, triggered from an option menubutton.  In thi
  74. ;;    style tk::posted-Mb identifies the posted menubutton.
  75. ;; 4. As a popup menu.  In this style tk::posted-mb is empty and
  76. ;;    the top-level menu is override-redirect.
  77. ;;
  78. ;; The various binding procedures use the  state described above to
  79. ;; distinguish the various cases and take different actions in each
  80. ;; case.
  81. ;;-------------------------------------------------------------------------
  82.  
  83. ;;-------------------------------------------------------------------------
  84. ;; The code below creates the default class bindings for menus
  85. ;; and menubuttons.
  86. ;;-------------------------------------------------------------------------
  87.  
  88.  
  89. (define-binding "Menubutton" "<FocusIn>" ()
  90.   '())
  91.  
  92. (define-binding "Menubutton" "<Enter>" (|W|)
  93.   (Tk:menu-button-enter |W|))
  94.  
  95. (define-binding "Menubutton" "<Leave>" (|W|)
  96.   (Tk:menu-button-leave |W|))
  97.  
  98. (define-binding "Menubutton" "<1>" (|W| |X| |Y|)
  99.   (when tk::in-menu-button
  100.      (Tk:menu-button-post tk::in-menu-button |X| |Y|)))
  101.  
  102. (define-binding "Menubutton" "<Motion>" (|W| |X| |Y|)
  103.   (Tk:menu-button-motion |W| 'up |X| |Y|))
  104.  
  105. (define-binding "Menubutton" "<B1-Motion>" (|W| |X| |Y|)
  106.   (Tk:menu-button-motion |W| 'down |X| |Y|))
  107.  
  108. (define-binding "Menubutton" "<ButtonRelease-1>" (|W|)
  109.   (Tk:menu-button-button-up |W|))
  110.  
  111. (define-binding "Menubutton" "<space>" (|W|)
  112.   (Tk:menu-button-post |W|)
  113.   (Tk:menu-first-entry (tk-get |W| :menu)))
  114.  
  115. ;; Must set focus when mouse enters a menu, in order to allow
  116. ;; mixed-mode processing using both the mouse and the keyboard.
  117. ;; Don't set the focus if the event comes from a grab release,
  118. ;; though:  such an event can happen after as part of unposting
  119. ;; a cascaded chain of menus, after the focus has already been
  120. ;; restored to wherever it was before menu selection started.
  121.  
  122. (define-binding "Menu" "<FocusIn>" ()
  123.   '())
  124.  
  125. (define-binding "Menu" "<Enter>" (|W| m)
  126.   (set! tk::window |W|)
  127.   (unless (string=? m "NotifyUngrab")
  128.     (focus |W|)))
  129.  
  130. (define-binding "Menu" "<Leave>" (|W| |X| |Y|)
  131.   (Tk:menu-leave |W| |X| |Y|))
  132.  
  133. (define-binding "Menu" "<Motion>" (|W| y s)
  134.   (Tk:menu-Motion |W| y s))
  135.  
  136. (define-binding "Menu" "<ButtonPress>" (|W|)
  137.   (Tk:menu-button-down |W|))
  138.  
  139. (define-binding "Menu" "<ButtonRelease>" (|W|)
  140.   (Tk:menu-invoke |W| #t))
  141.  
  142. (define-binding "Menu" "<space>" (|W|)
  143.   (Tk:menu-invoke |W| #f))
  144.  
  145. (define-binding "Menu" "<Return>" (|W|)
  146.   (Tk:menu-invoke |W| #f))
  147.  
  148. (define-binding "Menu" "<Escape>" (|W|)
  149.   (Tk:menu-escape |W|))
  150.  
  151. (define-binding "Menu" "<Left>" (|W|)
  152.   (Tk:menu-left-right |W| 'left))
  153.  
  154. (define-binding "Menu" "<Right>" (|W|)
  155.   (Tk:menu-left-right |W| 'right))
  156.  
  157. (define-binding "Menu" "<Up>" (|W|)
  158.   (Tk:menu-next-entry |W| -1))
  159.  
  160. (define-binding "Menu" "<Down>" (|W|)
  161.   (Tk:menu-next-entry |W| +1))
  162.  
  163. (define-binding "Menu" "<KeyPress>" (|W| |A|)
  164.   (Tk:traverse-within-menu |W| |A|))
  165.  
  166.  
  167. ;; The following bindings apply to all windows, and are used to
  168. ;; implement keyboard menu traversal.
  169.  
  170. (define-binding "all" "<Alt-KeyPress>" (|W| |A|)
  171.   (Tk:traverse-to-menu |W| |A|))
  172.  
  173. (define-binding "all" "<Meta-KeyPress>" (|W| |A|)
  174.   (Tk:traverse-to-menu |W| |A|))
  175.  
  176. (define-binding "all" "<F10>" (|W|)
  177.   (Tk:first-menu |W|))
  178.  
  179. ;; Tk:menu-button-enter --
  180. ;; This procedure is invoked when the mouse enters a menubutton
  181. ;; widget.  It activates the widget unless it is disabled.  Note:
  182. ;; this procedure is only invoked when mouse button 1 is *not* down.
  183. ;; The procedure Tk:menu-button-B1-enter is invoked if the button is down.
  184. ;;
  185.  
  186. (define (Tk:menu-button-enter w)
  187.   (when tk::in-menu-button
  188.      (Tk:menu-button-leave tk::in-menu-button))
  189.  
  190.   (set! tk::in-menu-button w)
  191.  
  192.   (unless (equal? (tk-get w :state) "disabled")
  193.      (tk-set! w :state "active")))
  194.  
  195. ;; Tk:menu-button-leave --
  196. ;; This procedure is invoked when the mouse leaves a menubutton widget.
  197. ;; It de-activates the widget, if the widget still exists.
  198. ;;
  199.  
  200. (define (Tk:menu-button-leave w)
  201.   (set! tk::in-menu-button #f)
  202.   (when (and (winfo 'exists w) (equal? (tk-get w :state) "active"))
  203.     (tk-set! w :state "normal")))
  204.  
  205. ;; Tk:menu-button-Post --
  206. ;; Given a menubutton, this procedure does all the work of posting
  207. ;; its associated menu and unposting any other menu that is currently
  208. ;; posted.
  209. ;;
  210. ;; w -            The name of the menubutton widget whose menu
  211. ;;            is to be posted.
  212. ;; x, y -        Root coordinates of cursor, used for positioning
  213. ;;            option menus.  If not specified, then the center
  214. ;;            of the menubutton is used for an option menu.
  215.  
  216. (define (Tk:menu-button-post w . coords)
  217.   (unless (or (equal? (tk-get w :state) "disabled") (equal? w tk::posted-mb))
  218.     (let ((menu (tk-get w :menu)))
  219.       (when menu
  220.     (unless (string-find? (string-append (widget->string w) ".")
  221.                   (widget->string menu))
  222.       ;; This is weak, but should be sufficient
  223.       (error "can't post ~S:  it isn't a descendant of ~S" menu w))
  224.  
  225.     (let ((cur tk::posted-mb))
  226.       (when tk::posted-mb (Tk:menu-unpost #f))
  227.       (set! tk::cursor (tk-get w :cursor))
  228.       (set! tk::relief (tk-get w :relief))
  229.       (tk-set! w :cursor "arrow")
  230.       (tk-set! w :relief "raised")
  231.       (set! tk::posted-mb w)
  232.       (set! tk::focus (focus))
  233.       (menu 'activate 'none)
  234.          
  235.       ;; If this looks like an option menubutton then post the menu so
  236.       ;; that the current entry is on top of the mouse. Otherwise post
  237.       ;; the menu just below the menubutton, as for a pull-down.
  238.       (if (tk-get w :indicatoron)
  239.           (let ((x (if (null? coords)
  240.                (+ (winfo 'rootx w) (/ (winfo 'width w) 2))
  241.                (car coords)))
  242.             (y (if (null? coords)
  243.                (+ (winfo 'rooty w) (/ (winfo 'height w) 2))
  244.                (cadr coords))))
  245.         (Tk:post-over-point menu x y
  246.                     (Tk:menu-find-name menu (tk-get w :text))))
  247.           (menu 'post (winfo 'rootx w)
  248.             (+ (winfo 'rooty w) (winfo 'height w))))
  249.       (focus menu)
  250.       (Tk:save-grab-info w)
  251.       (grab :global w))))))
  252.  
  253. ;; Tk:menu-unpost --
  254. ;; This procedure unposts a given menu, plus all of its ancestors up
  255. ;; to (and including) a menubutton, if any.  It also restores various
  256. ;; values to what they were before the menu was posted, and releases
  257. ;; a grab if there's a menubutton involved.  Special notes:
  258. ;; 1. It's important to unpost all menus before releasing the grab, so
  259. ;;    that any Enter-Leave events (e.g. from menu back to main
  260. ;;    application) have mode NotifyGrab.
  261. ;; 2. Be sure to enclose various groups of commands in "catch" so that
  262. ;;    the procedure will complete even if the menubutton or the menu
  263. ;;    or the grab window has been deleted.
  264. ;;
  265. ;; menu -        Name of a menu to unpost.  Ignored if there
  266. ;;            is a posted menubutton.
  267.  
  268. (define (Tk:menu-unpost menu)
  269.   (let ((mb tk::posted-mb))
  270.     ;; Restore focus right away (otherwise X will take focus away when
  271.     ;; the menu is unmapped and under some window managers (e.g. olvwm)
  272.     ;; we'll lose the focus completely).
  273.  
  274.     (catch (focus tk::focus))
  275.     (set! tk::focus #f)
  276.     ;; Unpost menu(s) and restore some stuff that's dependent on
  277.     ;; what was posted.
  278.     (BEGIN ;catch 
  279.        (if mb
  280.        (begin
  281.          (set! menu (tk-get mb :menu))
  282.          (menu 'unpost)
  283.          (set! tk::posted-mb #f)
  284.          (tk-set! mb :cursor tk::cursor :relief tk::relief))
  285.        (if tk::popup
  286.            (begin
  287.          (tk::popup 'unpost)
  288.          (set! tk::popup #f))
  289.            (when (and menu (wm 'overrideredirect menu))
  290.           ;; We're in a cascaded sub-menu from a torn-off menu or popup.
  291.           ;; Unpost all the menus up to the toplevel one (but not
  292.           ;; including the top-level torn-off one) and deactivate the
  293.           ;; top-level torn off menu if there is one.
  294.           (let loop ((parent (winfo 'parent menu)))
  295.             (when (and (equal? (winfo 'class parent) "Menu")
  296.                    (winfo 'ismapped parent))
  297.                (parent 'activate "none")
  298.                (parent 'postcascade "none")
  299.                (if (wm 'overrideredirect parent)
  300.                (loop (winfo 'parent parent)))))
  301.           (menu 'unpost)))))
  302.  
  303.     ;; Release grab, if any, and restore the previous grab, if there was one.
  304.     (if menu
  305.     (let ((g (grab 'current menu)))
  306.          (and g (grab 'release g))))
  307.     (when tk::old-grab
  308.       ;; Be careful restoring the old grab, since it's window may not
  309.       ;; be visible anymore.
  310.       (catch
  311.         (if (equal? tk::grab-status "global")
  312.         (grab 'set :global tk::old-grab)
  313.         (grab 'set tk::old-grab)))
  314.       (set! tk::old-grab #f))))
  315.  
  316. ;; Tk:menu-button-motion --
  317. ;; This procedure handles mouse motion events inside menubuttons, and
  318. ;; also outside menubuttons when a menubutton has a grab (e.g. when a
  319. ;; menu selection operation is in progress).
  320. ;;
  321. ;; w -            The name of the menubutton widget.
  322. ;; upDown -         "down" means button 1 is pressed, "up" means
  323. ;;            it isn't.
  324. ;; rootx, rooty -    Coordinates of mouse, in (virtual?) root window.
  325. (define (Tk:menu-button-motion w upDown rootx rooty)
  326.   (unless (equal? tk::in-menu-button w)
  327.      (let ((new (winfo 'containing rootx rooty)))
  328.        (when (and (not (equal? new tk::in-menu-button))
  329.           (or (not new)
  330.               (equal? (winfo 'toplevel new) (winfo 'toplevel w))))
  331.       (if tk::in-menu-button
  332.           (Tk:menu-button-leave tk::in-menu-button))
  333.       (when (and new 
  334.              (equal? (winfo 'class new) "Menubutton")
  335.              (not (tk-get new :indicatoron))
  336.              (not (tk-get w   :indicatoron)))
  337.          (if (eq? updown 'down)
  338.          (Tk:menu-button-post new rootx rooty)
  339.          (Tk:menu-button-enter new)))))))
  340.  
  341.  
  342. ;; Tk:menu-button-button-up --
  343. ;; This procedure is invoked to handle button 1 releases for menubuttons.
  344. ;; If the release happens inside the menubutton then leave its menu
  345. ;; posted with element 0 activated.  Otherwise, unpost the menu.
  346. ;;
  347. (define (Tk:menu-button-button-up w)
  348.   (if (and (equal? tk::posted-mb w) (equal? tk::in-menu-button w))
  349.       (Tk:menu-first-entry (tk-get tk::posted-mb :menu))
  350.       (Tk:menu-unpost #f)))
  351.  
  352.  
  353. ;; Tk:menu-motion --
  354. ;; This procedure is called to handle mouse motion events for menus.
  355. ;; It does two things.  First, it resets the active element in the
  356. ;; menu, if the mouse is over the menu.  Second, if a mouse button
  357. ;; is down, it posts and unposts cascade entries to match the mouse
  358. ;; position.
  359. ;;
  360. ;; Arguments:
  361. ;; menu -        The menu window.
  362. ;; y -            The y position of the mouse.
  363. ;; state -        Modifier state (tells whether buttons are down).
  364.  
  365. (define (Tk:menu-motion menu y state)
  366.   (if (equal? menu tk::window)
  367.       (menu 'activate (format #f "@~A" y)))
  368.   (if (= (* (modulo state 128) (modulo state 512) (modulo state 1024)) 0)
  369.       (menu 'postcascade 'active)))
  370.  
  371. ;; Tk:menu-button-down --
  372. ;; Handles button presses in menus.  There are a couple of tricky things
  373. ;; here:
  374. ;; 1. Change the posted cascade entry (if any) to match the mouse position.
  375. ;; 2. If there is a posted menubutton, must grab to the menubutton;  this
  376. ;;    overrrides the implicit grab on button press, so that the menu
  377. ;;    button can track mouse motions over other menubuttons and change
  378. ;;    the posted menu.
  379. ;; 3. If there's no posted menubutton (e.g. because we're a torn-off menu
  380. ;;    or one of its descendants) must grab to the top-level menu so that
  381. ;;    we can track mouse motions across the entire menu hierarchy.
  382. ;;
  383.  
  384. (define (Tk:menu-button-down menu)
  385.   (menu 'postcascade 'active)
  386.   (if tk::posted-mb 
  387.       (grab :global tk::posted-mb)
  388.       (let loop ((menu   menu)
  389.          (parent (winfo 'parent menu)))
  390.     (if (and (wm 'overrideredirect menu)
  391.          (equal? (winfo 'class parent) "Menu")
  392.          (winfo 'ismapped parent))
  393.         (loop parent (winfo 'parent parent))
  394.         (begin
  395.           ; Don't update grab information if the grab window isn't changing.
  396.           ; Otherwise, we'll get an error when we unpost the menus and
  397.           ; restore the grab, since the old grab window will not be viewable
  398.           ; anymore.
  399.           (unless (equal? menu (grab 'current menu))
  400.         (Tk:save-grab-info menu))
  401.           ; Must re-grab even if the grab window hasn't changed, in order
  402.           ; to release the implicit grab from the button press.
  403.           (grab :global menu))))))
  404.  
  405. ;; Tk:menu-leave --
  406. ;; This procedure is invoked to handle Leave events for a menu.  It
  407. ;; deactivates everything unless the active element is a cascade element
  408. ;; and the mouse is now over the submenu.
  409. ;;
  410. ;; menu -        The menu window.
  411. ;; rootx, rooty -    Root coordinates of mouse.
  412. ;; state -        Modifier state.
  413.  
  414. (define (Tk:menu-leave menu rootx rooty)
  415.   (set! tk::window #f)
  416.   (unless (equal? (menu 'index 'active) "none")
  417.     (unless (and (equal? (menu 'type "active") "cascade")
  418.          (equal? (winfo 'containing rootx rooty)
  419.              (menu 'entrycget 'active :menu)))
  420.        (menu 'activate "none"))))
  421.  
  422.  
  423. ;; Tk:menu-invoke --
  424. ;; This procedure is invoked when button 1 is released over a menu.
  425. ;; It invokes the appropriate menu action and unposts the menu if
  426. ;; it came from a menubutton.
  427. ;;
  428. ;;  w -            menu widget.
  429. ;; button-release -    #t means this procedure is called because of
  430. ;;            a button release;  #f means because of keystroke.
  431. ;;
  432. (define (Tk:menu-invoke w button-release)
  433.   (cond
  434.      ((and button-release (not tk::window))
  435.                   ;; Mouse was pressed over a menu without a menu button, 
  436.                   ;; then dragged off the menu (possibly with a cascade
  437.             ;; posted) and  released.  Unpost everything
  438.                   (w 'postcascade "none")
  439.             (w 'activate "none")
  440.             (Tk:menu-unpost w))
  441.      ((equal? (w 'type "active") "cascade")
  442.             (w 'postcascade "active")
  443.             (Tk:menu-first-entry (w 'entrycget "active" :menu)))
  444.      ((equal? (w 'type "active") "tearoff")
  445.                   (Tk:menu-unpost w)
  446.             (Tk:tear-off-menu w))
  447.      (ELSE        (Tk:menu-unpost w)
  448.             (w 'invoke "active"))))
  449.  
  450.  
  451. ;; Tk:menuEscape --
  452. ;; This procedure is invoked for the Cancel (or Escape) key.  It unposts
  453. ;; the given menu and, if it is the top-level menu for a menu button,
  454. ;; unposts the menu button as well.
  455. ;;
  456. (define (Tk:menu-escape menu)
  457.   (if (equal? (winfo 'class (winfo 'parent menu)) "Menu")
  458.       (Tk:menu-left-right menu -1)
  459.       (Tk:menu-unpost menu)))
  460.  
  461. ;; Tk:menu-left-right --
  462. ;; This procedure is invoked to handle "left" and "right" traversal
  463. ;; motions in menus.  It traverses to the next menu in a menu bar,
  464. ;; or into or out of a cascaded menu.
  465. ;;
  466. ;; menu -        The menu that received the keyboard
  467. ;;            event.
  468. ;; direction -        Direction in which to move: "left" or "right"
  469.  
  470. (define (Tk:menu-left-right menu direction)
  471.   (let ((count    +1)
  472.     (continue #t))
  473.     ;; First handle traversals into and out of cascaded menus.
  474.     (if (eq? direction 'right)
  475.     (when (equal? (menu 'type "active") "cascade")
  476.         (menu 'postcascade "active")
  477.         (let ((m2 (menu 'entrycget 'active :menu)))
  478.           (and m2 (Tk:menu-first-entry m2))
  479.           (set! continue #f)))
  480.     ;; Direction is 'left
  481.     (let ((m2 (winfo 'parent menu)))
  482.       (set! count -1)
  483.       (when (equal? (winfo 'class m2) "Menu")
  484.         (menu 'activate "none")
  485.         (focus m2)
  486.         ;; This code unposts any posted submenu in the parent.
  487.         (let ((tmp (m2 'index "active")))
  488.           (m2 'activate "none")
  489.           (m2 'activate tmp)
  490.           (set! continue #f)))))
  491.     
  492.     (when (and continue tk::posted-mb)
  493.       ;; Can't traverse into or out of a cascaded menu.  Go to the next
  494.       ;; or previous menubutton, if that makes sense.
  495.       (let* ((buttons (winfo 'children [winfo 'parent tk::posted-mb]))
  496.          (len     (length buttons)))
  497.     (let loop ((i (- count (length (member tk::posted-mb buttons)))))
  498.       (while (< i 0)     (set! i (+ i len)))
  499.       (while (>=  i len) (set! i (- i len)))
  500.       (let ((mb (list-ref buttons i)))
  501.         (when (and (equal? [winfo 'class mb] "Menubutton")
  502.                (not (equal? [tk-get mb :state] "disabled"))
  503.                (tk-get mb :menu)
  504.                (not (equal? ((tk-get mb :menu) 'index "last") "none")))
  505.           (Tk:menu-button-post mb)
  506.           (Tk:menu-first-entry (tk-get mb :menu)))
  507.         (unless (eq? mb tk::posted-mb)
  508.           (loop (+ i count)))))))))
  509.  
  510. ;; Tk:menu-next-entry --
  511. ;; Activate the next higher or lower entry in the posted menu,
  512. ;; wrapping around at the ends.  Disabled entries are skipped.
  513. ;;
  514. ;; Arguments:
  515. ;; menu -            Menu window that received the keystroke.
  516. ;; count -            1 means go to the next lower entry,
  517. ;;                -1 means go to the next higher entry.
  518.  
  519. (define (Tk:menu-next-entry menu count)
  520.   (unless (equal? (menu 'index "last") "none")
  521.     (let* ((length     (+ (menu 'index "last") 1))
  522.        (quit-after length)
  523.        (active     (menu 'index "active"))
  524.        (i          0)
  525.        (break      #f))
  526.  
  527.       (unless (equal? active "none")
  528.     (set! i (+ active count)))
  529.       
  530.       (let loop ((i i))
  531.     (when (> quit-after 0)
  532.       ;; We've not already tried every entry in the menu
  533.       (set! quit-after (- quit-after 1))
  534.  
  535.       (while (< i 0)        (set! i (+ i length)))
  536.       (while (>=  i length) (set! i (- i length)))
  537.  
  538.       (catch (set! break (not (equal? (menu 'entrycget i :state) "disabled"))))
  539.       (if break
  540.           (begin
  541.         (menu 'activate i)
  542.         (menu 'postcascade i))
  543.           (unless (= i active)
  544.         (loop (+ i count)))))))))
  545.  
  546. ;; Tk:menu-find --
  547. ;; This procedure searches the entire window hierarchy under w for
  548. ;; a menubutton that isn't disabled and whose underlined character
  549. ;; is "char".  It returns the name of that window, if found, or #f
  550. ;; if no matching window was found.  If "char" is an
  551. ;; empty string then the procedure returns the name of the first
  552. ;; menubutton found that isn't disabled.
  553. ;;
  554. ;; w -                Name of window where key was typed.
  555. ;; char -            Underlined character to search for;
  556. ;;                may be either upper or lower case, and
  557. ;;                will match either upper or lower case.
  558.  
  559. (define (Tk:menu-find w char)
  560.   (let ((char (string-lower char)))
  561.     (let loop ((children  (winfo 'children w)))
  562.       (if (null? children)
  563.       #f
  564.       (let* ((child (car children))
  565.          (C     (winfo 'class child)))
  566.         (cond
  567.          ((string=? C "Menubutton")
  568.              (let* ((index (tk-get child :underline))
  569.             (txt   (tk-get child :text))
  570.             (char2 (if (= index -1)
  571.                    "" 
  572.                    (string (string-ref txt index)))))
  573.            (if (and (or (string=? char (string-lower char2)) 
  574.                 (string=? char ""))
  575.                 (not (equal? (tk-get child :state) "disabled")))
  576.                child
  577.                (loop (cdr children)))))
  578.          ((string=? C "Frame")
  579.               (or (Tk:menu-find child char)
  580.               (loop (cdr children))))
  581.          (ELSE (loop (cdr children)))))))))
  582.  
  583. ;; Tk:traverse-to-menu --
  584. ;; This procedure implements keyboard traversal of menus.  Given an
  585. ;; ASCII character "char", it looks for a menubutton with that character
  586. ;; underlined.  If one is found, it posts the menubutton's menu
  587. ;;
  588. ;; Arguments:
  589. ;; w -                Window in which the key was typed (selects
  590. ;;                a toplevel window).
  591. ;; char -            Character that selects a menu.  The case
  592. ;;                is ignored.  If an empty string, nothing
  593. ;;                happens.
  594.  
  595. (define (Tk:traverse-to-menu w char)
  596.   (let ((continue #t))
  597.     (unless (string=? char "")
  598.       (while (and continue (equal? (winfo 'class w) "Menu"))
  599.        (if  tk::posted-mb
  600.        (set! w (winfo 'parent w))
  601.        (set! continue #f)))
  602.       (when continue
  603.     (let ((w (Tk:menu-find (winfo 'toplevel w) char)))
  604.       (when w 
  605.         (Tk:menu-button-post w)
  606.         (Tk:menu-first-entry (tk-get w :menu))))))))
  607.  
  608. ;; Tk:first-menu --
  609. ;; This procedure traverses to the first menubutton in the toplevel
  610. ;; for a given window, and posts that menubutton's menu.
  611. ;;
  612. ;; w -                Name of a window.  Selects which toplevel
  613. ;;                to search for menubuttons.
  614.  
  615. (define (Tk:first-menu w)
  616.   (let ((w (Tk:menu-find (winfo 'toplevel w) "")))
  617.     (when w
  618.       (Tk:menu-button-post w)
  619.       (Tk:menu-first-entry (tk-get w :menu)))))
  620.  
  621. ;; Tk:traverse-within-menu
  622. ;; This procedure implements keyboard traversal within a menu.  It
  623. ;; searches for an entry in the menu that has "char" underlined.  If
  624. ;; such an entry is found, it is invoked and the menu is unposted.
  625. ;;
  626. ;; Arguments:
  627. ;; w -                The name of the menu widget.
  628. ;; char -            The character to look for;  case is
  629. ;;                ignored.  If the string is empty then
  630. ;;                nothing happens.
  631.  
  632. (define (Tk:traverse-within-menu w char)
  633.   (unless (equal? char "")
  634.     (let* ((char (string-lower char))
  635.        (last (w 'index "last")))
  636.       (unless (equal? last "none")
  637.     (let loop ((i 0))
  638.       (when (<= i last)
  639.         (let ((char2 #f)
  640.           (index -1)
  641.           (label ""))
  642.           (catch 
  643.              (set! index (w 'entrycget i :underline))
  644.          (set! label (w 'entrycget i :label))
  645.          (set! char2 (string-lower (string (string-ref label index)))))
  646.           
  647.           (if (and char2 (string=? char char2))
  648.           (if (equal? (w 'type i) "cascade")
  649.               (begin
  650.             (w 'postcascade i)
  651.             (w 'activate i)
  652.             (let ((m2 (w 'entrycget i :menu)))
  653.               (and m2 (Tk:menu-first-entry m2))))
  654.               (begin
  655.             (Tk:menu-unpost w)
  656.             (w 'invoke i)))
  657.           (loop (+ i 1))))))))))
  658.  
  659. ;; Tk:menu-first-entry --
  660. ;; Given a menu, this procedure finds the first entry that isn't
  661. ;; disabled or a tear-off or separator, and activates that entry.
  662. ;; However, if there is already an active entry in the menu (e.g.,
  663. ;; because of a previous call to tkPostOverPoint) then the active
  664. ;; entry isn't changed.  This procedure also sets the input focus
  665. ;; to the menu.
  666. ;;
  667. (define (Tk:menu-first-entry menu)
  668.   (when menu
  669.       (focus menu)
  670.       (when (equal? (menu 'index "active") "none")
  671.       (let ((last (menu 'index "last")))
  672.         (unless (equal? last "none")
  673.             (let loop ((i 0))
  674.           (when (<= i last)
  675.               (let ((state #f))
  676.             (catch (set! state (menu 'entrycget i :state)))
  677.             (if (or (not state)
  678.                 (equal? state "disabled")
  679.                 (equal? (menu 'type i) "tearoff"))
  680.                 (loop (+ i 1))
  681.                 (menu 'activate i))))))))))
  682.  
  683.  
  684. ;; Tk:menu-find-name --
  685. ;; Given a menu and a text string, return the index of the menu entry
  686. ;; that displays the string as its label.  If there is no such entry,
  687. ;; return an empty string.  This procedure is tricky because some names
  688. ;; like "active" have a special meaning in menu commands, so we can't
  689. ;; always use the "index" widget command.
  690. ;;
  691. ;; menu -        Name of the menu widget.
  692. ;; s -            String to look for.
  693.  
  694. (define (Tk:menu-find-name menu s)
  695.   (let ((last (menu 'index "last")))
  696.     (unless (equal? last "none")
  697.       (let loop ((i 0))
  698.     (if (<= i last)
  699.         (let ((label #f))
  700.           (catch (set! label (menu 'entrycget i :label)))
  701.           (if (equal? label s)
  702.           i
  703.           (loop (+ i 1))))
  704.         #f)))))
  705.  
  706. ;; Tk:post-over-point --
  707. ;; This procedure posts a given menu such that a given entry in the
  708. ;; menu is centered over a given point in the root window.  It also
  709. ;; activates the given entry.
  710. ;;
  711. ;; menu -        Menu to post.
  712. ;; x, y -        Root coordinates of point.
  713. ;; entry -        Index of entry within menu to center over (x,y).
  714. ;;            If omitted or specified as {}, then the menu's
  715. ;;            upper-left corner goes at (x,y).
  716.  
  717. (define (Tk:post-over-point menu x y entry)
  718.   (when entry
  719.     (if (= entry (menu 'index "last"))
  720.     (set! y (- y (/ (+ (menu 'yposition entry) (winfo 'reqheight menu)) 2)))
  721.     (set! y (- y (/ (+ (menu 'yposition entry) 
  722.                (menu 'yposition (+ entry 1))) 2))))
  723.     (set! x (- x (/ (winfo 'reqwidth menu) 2))))
  724.  
  725.   (menu 'post (inexact->exact x) (inexact->exact y))
  726.   (if (and entry (not (equal? (menu 'entrycget entry :state) "disabled")))
  727.       (menu 'activate entry)))
  728.  
  729. ;; Tk:save-grab-info
  730. ;; Sets the variables tkPriv(oldGrab) and tkPriv(grabStatus) to record
  731. ;; the state of any existing grab on the w's display.
  732. ;;
  733. ;; w -            Name of a window;  used to select the display
  734. ;;            whose grab information is to be recorded.
  735.  
  736. (define (Tk:save-grab-info w)
  737.   (set! tk::old-grab (grab 'current w))
  738.   (if tk::old-grab
  739.       (set! tk::grab-status [grab 'status tk::old-grab])))
  740.  
  741. (load "tearoff")
  742.  
  743. ))
  744.  
  745. ;; Tk:popup --
  746. ;; This procedure pops up a menu and sets things up for traversing
  747. ;; the menu and its submenus.
  748. ;;
  749. ;; menu -        Name of the menu to be popped up.
  750. ;; x, y -        Root coordinates at which to pop up the
  751. ;;            menu.
  752. ;; entry -        Index of a menu entry to center over (x,y).
  753. ;;            If omitted or specified as {}, then menu's
  754. ;;            upper-left corner goes at (x,y).
  755.  
  756. (define (Tk:popup menu x y entry)
  757.   (if (or tk::popup tk::posted-mb)
  758.       (Tk:menu-unpost #f))
  759.   (Tk:post-over-point menu x y entry)
  760.   (Tk:save-grab-info menu)
  761.   (grab :global menu)
  762.   (set! tk::popup menu)
  763.   (set! tk::focus (focus))
  764.   (focus menu))
  765.  
  766. ;; Tk:option-menu --
  767. ;; This procedure creates an option button named  and an associated
  768. ;; menu.  Together they provide the functionality of Motif option menus:
  769. ;; they can be used to select one of many values, and the current value
  770. ;; appears in the global variable var-name, as well as in the text of
  771. ;; the option menubutton.  The  menu is returned as the
  772. ;; procedure's result, so that the caller can use it to change configuration
  773. ;; options on the menu or otherwise manipulate it.
  774. ;;
  775. ;; w -            The name to use for the menubutton.
  776. ;; var-name -        Global variable to hold the currently selected value.
  777. ;; first -        first (mandatory) legal value for option
  778. ;; l             legal values for option
  779.  
  780. (define (Tk:option-menu w var-name first . l)
  781.   ;; define var-name before (if necessary) otherwise :textvar will define it to ""
  782.   (unless (symbol-bound? var-name (global-environment))
  783.     (eval `(define ,var-name ',first) (global-environment)))
  784.  
  785.   (let* ((menu-name (format #f "~A.menu" w))
  786.      (mb         (menubutton w :textvariable var-name 
  787.                   :indicatoron #t
  788.                   :menu menu-name
  789.                   :relief "raised"
  790.                   :borderwidth 2 
  791.                   :highlightthickness 2
  792.                   :anchor "c"))
  793.      (m        (menu menu-name :tearoff #f))
  794.      (env        (global-environment)))
  795.  
  796.     (for-each (lambda (x)
  797.         (m 'add 'command :label x :command (lambda()
  798.                              (eval `(set! ,var-name ',x) 
  799.                                env))))
  800.           (cons first l))
  801.     m))
  802.